package com.varian.security.authorization;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.varian.tool.Func;
import com.varian.tool.exception.ServerException;
import jakarta.annotation.security.PermitAll;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.util.Map;
import java.util.Set;

/**
 * @author ben
 * @since 2024/6/23
 */
@Slf4j
@RequiredArgsConstructor
public class DefaultAuthorizeHttpRequestCustomizer implements AuthorizeHttpRequestCustomizer {

    private final RequestMappingHandlerMapping requestMappingHandlerMapping;

    private final Set<String> permitAllUrls = CollUtil.newHashSet();

    @Override
    public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
        AntPathRequestMatcher[] requestMatchers = new AntPathRequestMatcher[]{
                AntPathRequestMatcher.antMatcher("/actuator/**"),
                AntPathRequestMatcher.antMatcher("/css/**"),
                AntPathRequestMatcher.antMatcher("/error"),
                AntPathRequestMatcher.antMatcher("/favicon.**"),
                AntPathRequestMatcher.antMatcher("/v3/api-docs")
        };
        registry.requestMatchers(requestMatchers).permitAll();
        resolvePermitAllByAnnotation(registry);
        if (!Func.isProd() && CollUtil.isNotEmpty(permitAllUrls)) {
            log.info("\n初始化默认白名单路径: \n{}", StrUtil.join(StrUtil.LF, permitAllUrls));
        }
    }

    private void resolvePermitAllByAnnotation(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
        // 获取所有处理器方法及其映射信息
        Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
        // 遍历处理器方法，解析并添加到无需授权访问的列表
        handlerMethods.forEach((info, handlerMethod) -> {
            resolvePermitAllByMethod(info, handlerMethod, registry);
            resolvePermitAllByClass(handlerMethod.getBeanType(), registry);
        });
    }

    /**
     * 为类级别标注了@PermitAll的控制器开启所有请求的访问权限。
     *
     * @param beanType 控制器类类型
     * @param registry 用于配置授权请求匹配器的注册表
     */
    private void resolvePermitAllByClass(Class<?> beanType, AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
        // 检查类是否标注了@PermitAll和@RequestMapping注解
        if (beanType.isAnnotationPresent(PermitAll.class)
                && beanType.isAnnotationPresent(RequestMapping.class)) {
            RequestMapping requestMappingAnnotation = beanType.getAnnotation(RequestMapping.class);
            // 遍历映射路径，添加到无需授权访问的列表
            for (String path : requestMappingAnnotation.value()) {
                String fullPath = validateAndAppendPath(path);
                if (!permitAllUrls.contains(fullPath)) {
                    permitAllUrls.add(fullPath);
                    registry.requestMatchers(fullPath).permitAll();
                }
            }
        }
    }

    /**
     * 为方法级别标注了@PermitAll的方法开启所有请求的访问权限。
     *
     * @param info          请求映射信息
     * @param handlerMethod 处理器方法
     * @param registry      用于配置授权请求匹配器的注册表
     */
    private void resolvePermitAllByMethod(RequestMappingInfo info, HandlerMethod handlerMethod, AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {
        // 检查方法是否标注了@PermitAll注解
        if (handlerMethod.getMethodAnnotation(PermitAll.class) != null) {
            // 遍历方法映射的HTTP方法和路径，添加到无需授权访问的列表
            String prefixPath = handlerMethod.getBeanType().getAnnotation(RestController.class).value();
            info.getMethodsCondition().getMethods().forEach(method -> {
                info.getDirectPaths().forEach(path -> {
                    String fullPath = prefixPath + path;
                    String resolvePath = StrUtil.format("{} {}", method.asHttpMethod().name(), fullPath);
                    if (!permitAllUrls.contains(resolvePath)) {
                        permitAllUrls.add(resolvePath);
                        registry.requestMatchers(method.asHttpMethod(), fullPath).permitAll();
                    }
                });
            });
        }
    }

    /**
     * 验证路径并添加通配符。
     *
     * @param path 输入的路径
     * @return 经过验证和处理的路径
     * @throws ServerException 如果路径为空，则抛出异常
     */
    private String validateAndAppendPath(String path) {
        if (path == null || path.isEmpty()) {
            throw ServerException.of("Path cannot be null or empty");
        }
        // 为路径添加通配符
        return path + "/**";
    }
}
